
/******************************************************************************
 * MODULE     : qt_tm_widget.cpp
 * DESCRIPTION: The main TeXmacs input widget and its embedded counterpart.
 * COPYRIGHT  : (C) 2008  Massimiliano Gubinelli
 *******************************************************************************
 * This software falls under the GNU general public license version 3 or later.
 * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
 * in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.
 ******************************************************************************/

#include <QApplication>
#include <QComboBox>
#include <QDialog>
#include <QDockWidget>
#include <QLayoutItem>
#include <QMainWindow>
#include <QMenuBar>
#include <QScreen>
#include <QStatusBar>
#include <QToolBar>
#include <QToolButton>

#include "analyze.hpp"
#include "config.h"
#include "scheme.hpp"

#include "qt_gui.hpp"
#include "qt_picture.hpp"
#include "qt_renderer.hpp"
#include "qt_tm_widget.hpp"
#include "qt_utilities.hpp"

#include "QTMGuiHelper.hpp" // needed to connect()
#include "QTMInteractiveInputHelper.hpp"
#include "QTMInteractivePrompt.hpp"
#include "QTMStyle.hpp" // qtstyle()
#include "QTMWindow.hpp"
#include "preferences.hpp"
#include "qt_dialogues.hpp"
#include "qt_menu.hpp"
#include "qt_simple_widget.hpp"
#include "qt_window_widget.hpp"

#include <moebius/data/scheme.hpp>

using moebius::data::scm_quote;

int menu_count= 0; // zero if no menu is currently being displayed
list<qt_tm_widget_rep*> waiting_widgets;

static void
replaceActions (QWidget* dest, QList<QAction*>* src) {
  // NOTE: the parent hierarchy of the actions is not modified while installing
  //       the menu in the GUI (see qt_menu.hpp for this memory management
  //       policy)
  if (src == NULL || dest == NULL)
    TM_FAILED ("replaceActions expects valid objects");
  dest->setUpdatesEnabled (false);
  QList<QAction*> list= dest->actions ();
  for (int i= 0; i < list.count (); i++) {
    QAction* a= list[i];
    dest->removeAction (a);
  }
  for (int i= 0; i < src->count (); i++) {
    QAction* a= (*src)[i];
    dest->addAction (a);
  }
  dest->setUpdatesEnabled (true);
}

static void
replaceButtons (QToolBar* dest, QList<QAction*>* src) {
  if (src == NULL || dest == NULL)
    TM_FAILED ("replaceButtons expects valid objects");
  dest->setUpdatesEnabled (false);
  bool visible= dest->isVisible ();
  if (visible) dest->hide (); // TRICK: to avoid flicker of the dest widget
  replaceActions (dest, src);
  QList<QObject*> list= dest->children ();
  for (int i= 0; i < list.count (); ++i) {
    QToolButton* button= qobject_cast<QToolButton*> (list[i]);
    if (button) {
      button->setPopupMode (QToolButton::InstantPopup);
      if (tm_style_sheet == "") button->setStyle (qtmstyle ());
    }
  }
  if (visible) dest->show (); // TRICK: see above
  dest->setUpdatesEnabled (true);
}

void
QTMInteractiveInputHelper::commit (int result) {
  if (wid && result == QDialog::Accepted) {
    QString    item= "#f";
    QComboBox* cb  = sender ()->findChild<QComboBox*> ("input");
    if (cb) item= cb->currentText ();
    static_cast<qt_input_text_widget_rep*> (wid->int_input.rep)->input=
        from_qstring (item);
    static_cast<qt_input_text_widget_rep*> (wid->int_input.rep)->cmd ();
  }
  sender ()->deleteLater ();
}

/******************************************************************************
 * qt_tm_widget_rep
 ******************************************************************************/

qt_tm_widget_rep::qt_tm_widget_rep (int mask, command _quit)
    : qt_window_widget_rep (new QTMWindow (0), "popup", _quit), helper (this),
      prompt (NULL), full_screen (false) {
  type= texmacs_widget;

  main_widget= concrete (::glue_widget (true, true, 1, 1));

  // decode mask
  visibility[0]= (mask & 1) == 1;     // header
  visibility[1]= (mask & 2) == 2;     // main
  visibility[2]= (mask & 4) == 4;     // mode
  visibility[3]= (mask & 8) == 8;     // focus
  visibility[4]= (mask & 16) == 16;   // user
  visibility[5]= (mask & 32) == 32;   // footer
  visibility[6]= (mask & 64) == 64;   // side tools #0
  visibility[7]= (mask & 128) == 128; // bottom tools
  visibility[8]= (mask & 256) == 256; // tab page bar

#ifdef OS_WASM
  visibility[1]= false; // main
  visibility[2]= false; // mode
  visibility[3]= false; // focus
  visibility[4]= false; // user
  visibility[8]= false; // tab page bar
#endif

  // general setup for main window

  QMainWindow* mw= mainwindow ();
  if (tm_style_sheet == "") {
    mw->setStyle (qtmstyle ());
    mw->menuBar ()->setStyle (qtmstyle ());
  }

#ifdef Q_OS_MAC
  if (!use_native_menubar) {
    mw->menuBar ()->setNativeMenuBar (false);
    if (tm_style_sheet != "") {
      int min_h= (int) floor (28 * retina_scale);
      mw->menuBar ()->setMinimumHeight (min_h);
    }
  }
#else
  if (tm_style_sheet != "") {
    int min_h= (int) floor (28 * retina_scale);
    mw->menuBar ()->setMinimumHeight (min_h);
  }
#endif

  mw->setIconSize (QSize (17, 17));
  mw->setFocusPolicy (Qt::NoFocus);

  // status bar

  QStatusBar* bar= new QStatusBar (mw);
  leftLabel      = new QLabel (qt_translate ("Welcome to TeXmacs"), mw);
  rightLabel     = new QLabel (qt_translate ("Booting"), mw);
  leftLabel->setFrameStyle (QFrame::NoFrame);
  rightLabel->setFrameStyle (QFrame::NoFrame);
  leftLabel->setIndent (8);
  bar->addWidget (leftLabel, 1);
  bar->addPermanentWidget (rightLabel);
  if (tm_style_sheet == "") bar->setStyle (qtmstyle ());

  // NOTE (mg): the following setMinimumWidth command disable automatic
  // enlarging of the status bar and consequently of the main window due to
  // long messages in the left label. I found this strange solution here
  // http://www.archivum.info/qt-interest@trolltech.com/2007-05/01453/Re:-QStatusBar-size.html
  // The solution if due to Martin Petricek. He adds:
  //    The docs says: If minimumSize() is set, the minimum size hint will be
  //    ignored. Probably the minimum size hint was size of the lengthy message
  //    and internal layout was enlarging the satusbar and the main window Maybe
  //    the notice about QLayout that is at minimumSizeHint should be also at
  //    minimumSize, didn't notice it first time and spend lot of time trying to
  //    figure this out :)

  bar->setMinimumWidth (2);
#ifdef Q_OS_LINUX
  int min_h= (int) floor (28 * retina_scale);
  bar->setMinimumHeight (min_h);
#else
  if (tm_style_sheet != "") {
    int min_h= (int) floor (28 * retina_scale);
    bar->setMinimumHeight (min_h);
  }
#endif
  mw->setStatusBar (bar);

  // toolbars

  mainToolBar = new QToolBar ("main toolbar", mw);
  modeToolBar = new QToolBar ("mode toolbar", mw);
  focusToolBar= new QToolBar ("focus toolbar", mw);
  userToolBar = new QToolBar ("user toolbar", mw);

  bottomTools= new QDockWidget ("bottom tools", mw);
  sideTools  = new QDockWidget ("side tools", 0);
  tabToolBar = new QTMTabPageBar ("tab toolbar", mw);
  // HACK: Wrap the dock in a "fake" window widget (last parameter = true) to
  // have clicks report the right position.
  static int cnt      = 0;
  string     dock_name= "dock:" * as_string (cnt++);
  dock_window_widget=
      tm_new<qt_window_widget_rep> (sideTools, dock_name, command (), true);

  if (tm_style_sheet == "") {
    mainToolBar->setStyle (qtmstyle ());
    modeToolBar->setStyle (qtmstyle ());
    focusToolBar->setStyle (qtmstyle ());
    userToolBar->setStyle (qtmstyle ());
    sideTools->setStyle (qtmstyle ());
    bottomTools->setStyle (qtmstyle ());
    tabToolBar->setStyle (qtmstyle ());
  }

  {
    // set proper sizes for icons
    double scale= max (retina_scale, (double) retina_icons);
    QSize  sz   = QSize (int (24 * scale), int (24 * scale));
    tweak_iconbar_size (sz);
    mainToolBar->setIconSize (sz);
    sz= QSize (int (20 * scale), int (20 * scale));
    tweak_iconbar_size (sz);
    modeToolBar->setIconSize (sz);
    sz= QSize (int (16 * scale), int (16 * scale));
    tweak_iconbar_size (sz);
    focusToolBar->setIconSize (sz);
  }

  // Why we need fixed height:
  // The height of the toolbar is actually determined by the font height.
  // And the font height is not fixed. If the height of the toolbar is not
  // fixed, the stretching of it will make the document area floating and
  // triggers the re-rendering of the full document.
  //
  // NOTICE: setFixedHeight must be after setIconSize
  // TODO: the size of the toolbar should be calculated dynamically
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
  int toolbarHeight= 30 * retina_icons;
  mainToolBar->setFixedHeight (toolbarHeight + 8 * retina_icons);
  modeToolBar->setFixedHeight (toolbarHeight + 4 * retina_icons);
  focusToolBar->setFixedHeight (toolbarHeight);
  tabToolBar->setRowHeight (toolbarHeight + 4 * retina_icons);
#else
  int toolbarHeight= 30;
  mainToolBar->setFixedHeight (toolbarHeight + 8);
  modeToolBar->setFixedHeight (toolbarHeight + 4);
  focusToolBar->setFixedHeight (toolbarHeight);
  tabToolBar->setRowHeight (toolbarHeight + 4);
#endif
  if (tm_style_sheet != "") {
    double scale= retina_scale;
    int    h1   = (int) floor (38 * scale + 0.5);
    int    h2   = (int) floor (34 * scale + 0.5);
    int    h3   = (int) floor (30 * scale + 0.5);
    mainToolBar->setFixedHeight (h1);
    modeToolBar->setFixedHeight (h2);
    focusToolBar->setFixedHeight (h3);
    tabToolBar->setRowHeight (h2);
  }

  QWidget* cw= new QWidget ();
  cw->setObjectName (
      "centralWidget"); // this is important for styling toolbars.

  // The main layout

  QVBoxLayout* bl= new QVBoxLayout (cw);
  bl->setContentsMargins (0, 1, 0, 0);
  bl->setSpacing (0);
  cw->setLayout (bl);
  QWidget* q= main_widget->as_qwidget (); // force creation of QWidget
  q->setParent (
      qwid); // q->layout()->removeWidget(q) will reset the parent to this
  bl->addWidget (q);

  mw->setCentralWidget (cw);

  mainToolBar->setObjectName ("mainToolBar");
  modeToolBar->setObjectName ("modeToolBar");
  focusToolBar->setObjectName ("focusToolBar");
  userToolBar->setObjectName ("userToolBar");
  bottomTools->setObjectName ("bottomTools");
  sideTools->setObjectName ("sideTools");
  tabToolBar->setObjectName ("tabToolBar");

#ifdef UNIFIED_TOOLBAR

  if (use_unified_toolbar) {
    mw->setUnifiedTitleAndToolBarOnMac (true);

    // WARNING: dumbToolBar is the toolbar installed on the top area of the
    // main widget which is  then unified in the title bar.
    // to overcome some limitations of the unified toolbar implementation we
    // install the real toolbars as widgets in this toolbar.

    dumbToolBar= mw->addToolBar ("dumb toolbar");
    dumbToolBar->setMinimumHeight (30);

    // these are the actions related to the various toolbars to be installed in
    // the dumb toolbar.

    mainToolBarAction= dumbToolBar->addWidget (mainToolBar);
    modeToolBarAction= NULL;

    // A ruler
    rulerWidget= new QWidget (cw);
    rulerWidget->setSizePolicy (QSizePolicy::Ignored, QSizePolicy::Fixed);
    rulerWidget->setMinimumHeight (1);
    rulerWidget->setBackgroundRole (QPalette::Mid);
    // FIXME: how to use 112 (active) and 146 (passive)
    rulerWidget->setVisible (false);
    rulerWidget->setAutoFillBackground (true);
    // rulerWidget = new QLabel("pippo", cw);

    // A second ruler (this one always visible) to separate from the canvas.
    QWidget* r2= new QWidget (mw);
    r2->setSizePolicy (QSizePolicy::Ignored, QSizePolicy::Fixed);
    r2->setMinimumHeight (1);
    r2->setBackgroundRole (QPalette::Mid);
    r2->setVisible (true);
    r2->setAutoFillBackground (true);

    bl->insertWidget (0, tabToolBar);
    bl->insertWidget (1, modeToolBar);
    bl->insertWidget (2, rulerWidget);
    bl->insertWidget (3, focusToolBar);
    bl->insertWidget (4, userToolBar);
    bl->insertWidget (6, r2);

    // mw->setContentsMargins (-2, -2, -2, -2);  // Why this?
    bar->setContentsMargins (0, 1, 0, 1);
  }
  else {
    mw->addToolBar (mainToolBar);
    mw->addToolBarBreak ();
    mw->addToolBar (tabToolBar);
    mw->addToolBarBreak ();
    mw->addToolBar (modeToolBar);
    mw->addToolBarBreak ();
    mw->addToolBar (focusToolBar);
    mw->addToolBarBreak ();
    mw->addToolBar (userToolBar);
    mw->addToolBarBreak ();
  }

#else
  mw->addToolBar (mainToolBar);
  mw->addToolBarBreak ();
  mw->addToolBar (tabToolBar);
  mw->addToolBarBreak ();
  mw->addToolBar (modeToolBar);
  mw->addToolBarBreak ();
  mw->addToolBar (focusToolBar);
  mw->addToolBarBreak ();
  mw->addToolBar (userToolBar);
  mw->addToolBarBreak ();
#endif

  sideTools->setAllowedAreas (Qt::AllDockWidgetAreas);
  sideTools->setFeatures (QDockWidget::DockWidgetMovable |
                          QDockWidget::DockWidgetFloatable);
  sideTools->setFloating (false);
  sideTools->setTitleBarWidget (new QWidget ()); // Disables title bar
  mw->addDockWidget (Qt::RightDockWidgetArea, sideTools);

  bottomTools->setAllowedAreas (Qt::BottomDockWidgetArea);
  bottomTools->setFeatures (QDockWidget::NoDockWidgetFeatures);
  bottomTools->setFloating (false);
  bottomTools->setTitleBarWidget (new QWidget ()); // Disables title bar
  bottomTools->setMinimumHeight (10);              // Avoids warning
  bottomTools->setContentsMargins (3, 6, 3, -2);   // Hacks hacks hacks... :(
  mw->addDockWidget (Qt::BottomDockWidgetArea, bottomTools);

  // FIXME? add DockWidgetClosable and connect the close signal
  // to the scheme code
  //  QObject::connect(sideDock, SIGNAL(closeEvent()),
  //                   someHelper, SLOT(call_scheme_hide_side_tools()));

  // handles visibility
  // at this point all the toolbars are empty so we avoid showing them
  // same for the menu bar if we are not on the Mac (where we do not have
  // other options)

  mainToolBar->setVisible (false);
  modeToolBar->setVisible (false);
  focusToolBar->setVisible (false);
  userToolBar->setVisible (false);
  sideTools->setVisible (false);
  bottomTools->setVisible (false);
  tabToolBar->setVisible (false);
  mainwindow ()->statusBar ()->setVisible (true);
#ifndef Q_OS_MAC
  mainwindow ()->menuBar ()->setVisible (false);
#endif
  QPalette pal;
  QColor   bgcol= to_qcolor (tm_background);
  pal.setColor (QPalette::Mid, bgcol);
  mainwindow ()->setPalette (pal);
}

qt_tm_widget_rep::~qt_tm_widget_rep () {
  if (DEBUG_QT_WIDGETS)
    debug_widgets << "qt_tm_widget_rep::~qt_tm_widget_rep of widget "
                  << type_as_string () << LF;

  // clear any residual waiting menu installation
  waiting_widgets= remove (waiting_widgets, this);
}

void
qt_tm_widget_rep::tweak_iconbar_size (QSize& sz) {
#ifdef Q_OS_LINUX
  if (sz.height () >= 24) {
    sz.setWidth (sz.width () + 2);
    sz.setHeight (sz.height () + 8);
  }
  else if (sz.height () >= 20) {
    sz.setWidth (sz.width () + 1);
    sz.setHeight (sz.height () + 4);
  }
  else if (sz.height () >= 16) {
    sz.setHeight (sz.height () + 4);
  }
#else
  if (sz.height () >= 24) {
    sz.setWidth (sz.width () + 2);
    sz.setHeight (sz.height () + 6);
  }
  else if (sz.height () >= 20) {
    sz.setHeight (sz.height () + 2);
  }
  else if (sz.height () >= 16) {
    sz.setHeight (sz.height () + 2);
  }
#endif
  // sz.setHeight ((int) floor (sz.height () * retina_scale + 0.5));
}

/*! Return ourselves as a window widget.
 \param name A unique identifier for the window (e.g. "TeXmacs:3")
 */
widget
qt_tm_widget_rep::plain_window_widget (string name, command _quit, int b) {
  (void) b;
  (void) _quit; // The widget already has a command. Don't overwrite.
  orig_name= name;
  return this;
}

void
qt_tm_widget_rep::update_visibility () {
#define XOR(exp1, exp2) (((!exp1) && (exp2)) || ((exp1) && (!exp2)))

  bool old_mainVisibility  = mainToolBar->isVisible ();
  bool old_modeVisibility  = modeToolBar->isVisible ();
  bool old_focusVisibility = focusToolBar->isVisible ();
  bool old_userVisibility  = userToolBar->isVisible ();
  bool old_sideVisibility  = sideTools->isVisible ();
  bool old_bottomVisibility= bottomTools->isVisible ();
  bool old_tabVisibility   = tabToolBar->isVisible ();
  bool old_statusVisibility= mainwindow ()->statusBar ()->isVisible ();

  bool new_mainVisibility  = visibility[1] && visibility[0];
  bool new_modeVisibility  = visibility[2] && visibility[0];
  bool new_focusVisibility = visibility[3] && visibility[0];
  bool new_userVisibility  = visibility[4] && visibility[0];
  bool new_statusVisibility= visibility[5];
  bool new_sideVisibility  = visibility[6];
  bool new_bottomVisibility= visibility[7];
  bool new_tabVisibility   = visibility[8] && visibility[0];

  if (XOR (old_mainVisibility, new_mainVisibility))
    mainToolBar->setVisible (new_mainVisibility);
  if (XOR (old_modeVisibility, new_modeVisibility))
    modeToolBar->setVisible (new_modeVisibility);
  if (XOR (old_focusVisibility, new_focusVisibility))
    focusToolBar->setVisible (new_focusVisibility);
  if (XOR (old_userVisibility, new_userVisibility))
    userToolBar->setVisible (new_userVisibility);
  if (XOR (old_sideVisibility, new_sideVisibility))
    sideTools->setVisible (new_sideVisibility);
  if (XOR (old_bottomVisibility, new_bottomVisibility))
    bottomTools->setVisible (new_bottomVisibility);
  if (XOR (old_tabVisibility, new_tabVisibility))
    tabToolBar->setVisible (new_tabVisibility);
  if (XOR (old_statusVisibility, new_statusVisibility))
    mainwindow ()->statusBar ()->setVisible (new_statusVisibility);

#ifndef Q_OS_MAC
  bool old_menuVisibility= mainwindow ()->menuBar ()->isVisible ();
  bool new_menuVisibility= visibility[0];

  if (XOR (old_menuVisibility, new_menuVisibility))
    mainwindow ()->menuBar ()->setVisible (new_menuVisibility);
#endif

// #if 0
#ifdef UNIFIED_TOOLBAR

  // do modifications only if needed to reduce flicker
  if (use_unified_toolbar && (XOR (old_mainVisibility, new_mainVisibility) ||
                              XOR (old_modeVisibility, new_modeVisibility))) {
    // ensure that the topmost visible toolbar is always unified on Mac
    // (actually only for main and mode toolbars, unifying focus is not
    // appropriate)

    QBoxLayout* bl=
        qobject_cast<QBoxLayout*> (mainwindow ()->centralWidget ()->layout ());

    if (modeToolBarAction)
      modeToolBarAction->setVisible (modeToolBar->isVisible ());
    mainToolBarAction->setVisible (mainToolBar->isVisible ());

    // WARNING: jugglying around bugs in Qt unified toolbar implementation
    // do not try to change the order of the following operations....

    if (mainToolBar->isVisible ()) {
      bool tmp= modeToolBar->isVisible ();
      dumbToolBar->removeAction (modeToolBarAction);
      dumbToolBar->addAction (mainToolBarAction);
      bl->insertWidget (0, rulerWidget);
      bl->insertWidget (0, modeToolBar);
      mainToolBarAction->setVisible (true);
      rulerWidget->setVisible (true);
      modeToolBar->setVisible (tmp);
      if (modeToolBarAction) modeToolBarAction->setVisible (tmp);
      dumbToolBar->setVisible (true);
    }
    else {
      dumbToolBar->removeAction (mainToolBarAction);
      if (modeToolBar->isVisible ()) {
        bl->removeWidget (rulerWidget);
        rulerWidget->setVisible (false);
        bl->removeWidget (modeToolBar);
        if (modeToolBarAction == NULL) {
          modeToolBarAction= dumbToolBar->addWidget (modeToolBar);
        }
        else {
          dumbToolBar->addAction (modeToolBarAction);
        }
        dumbToolBar->setVisible (true);
      }
      else {
        dumbToolBar->setVisible (false);
        dumbToolBar->removeAction (modeToolBarAction);
      }
    }
  }
  else {
    bool old_menuVisibility= mainwindow ()->menuBar ()->isVisible ();
    bool new_menuVisibility= visibility[0];

    if (XOR (old_menuVisibility, new_menuVisibility))
      mainwindow ()->menuBar ()->setVisible (new_menuVisibility);
  }
#endif // UNIFIED_TOOLBAR
#undef XOR
  if (tm_style_sheet == "" && use_mini_bars) {
    QFont f = leftLabel->font ();
    int   fs= as_int (get_preference ("gui:mini-fontsize", QTM_MINI_FONTSIZE));
    f.setPointSize (qt_zoom (fs > 0 ? fs : QTM_MINI_FONTSIZE));
    leftLabel->setFont (f);
    rightLabel->setFont (f);
  }
}

widget
qt_tm_widget_rep::read (slot s, blackbox index) {
  widget ret;

  switch (s) {
  case SLOT_CANVAS:
    check_type_void (index, s);
    ret= abstract (main_widget);
    break;

  default:
    return qt_window_widget_rep::read (s, index);
  }

  if (DEBUG_QT_WIDGETS)
    debug_widgets << "qt_tm_widget_rep::read " << slot_name (s)
                  << "\t\tfor widget\t" << type_as_string () << LF;

  return ret;
}

void
qt_tm_widget_rep::send (slot s, blackbox val) {
  switch (s) {
  case SLOT_INVALIDATE:
  case SLOT_INVALIDATE_ALL:
  case SLOT_EXTENTS:
  case SLOT_SCROLL_POSITION:
  case SLOT_ZOOM_FACTOR:
  case SLOT_MOUSE_GRAB:
    main_widget->send (s, val);
    return;
  case SLOT_KEYBOARD_FOCUS: {
    check_type<bool> (val, s);
    bool focus= open_box<bool> (val);
    if (focus && canvas () && !canvas ()->hasFocus ())
      canvas ()->setFocus (Qt::OtherFocusReason);
  } break;
  case SLOT_HEADER_VISIBILITY: {
    check_type<bool> (val, s);
    visibility[0]= open_box<bool> (val);
    update_visibility ();
  } break;
  case SLOT_MAIN_ICONS_VISIBILITY: {
    check_type<bool> (val, s);
    visibility[1]= open_box<bool> (val);
    update_visibility ();
  } break;
  case SLOT_MODE_ICONS_VISIBILITY: {
    check_type<bool> (val, s);
    visibility[2]= open_box<bool> (val);
    update_visibility ();
  } break;
  case SLOT_FOCUS_ICONS_VISIBILITY: {
    check_type<bool> (val, s);
    visibility[3]= open_box<bool> (val);
    update_visibility ();
  } break;
  case SLOT_USER_ICONS_VISIBILITY: {
    check_type<bool> (val, s);
    visibility[4]= open_box<bool> (val);
    update_visibility ();
  } break;

  case SLOT_FOOTER_VISIBILITY: {
    check_type<bool> (val, s);
    visibility[5]= open_box<bool> (val);
    update_visibility ();
  } break;
  case SLOT_SIDE_TOOLS_VISIBILITY: {
    check_type<bool> (val, s);
    visibility[6]= open_box<bool> (val);
    update_visibility ();
  } break;
  case SLOT_BOTTOM_TOOLS_VISIBILITY: {
    check_type<bool> (val, s);
    visibility[7]= open_box<bool> (val);
    update_visibility ();
  } break;
  case SLOT_TAB_PAGES_VISIBILITY: {
    check_type<bool> (val, s);
    visibility[8]= open_box<bool> (val);
    update_visibility ();
  } break;

  case SLOT_LEFT_FOOTER: {
    check_type<string> (val, s);
    string msg= open_box<string> (val);
    leftLabel->setText (to_qstring (msg));
    leftLabel->update ();
  } break;
  case SLOT_RIGHT_FOOTER: {
    check_type<string> (val, s);
    string msg= open_box<string> (val);
    rightLabel->setText (to_qstring (msg));
    rightLabel->update ();
  } break;
  case SLOT_SCROLLBARS_VISIBILITY:
    // ignore this: qt handles scrollbars independently
    //                send_int (THIS, "scrollbars", val);
    break;
  case SLOT_INTERACTIVE_MODE: {
    check_type<bool> (val, s);

    if (open_box<bool> (val) == true) {
      prompt= new QTMInteractivePrompt (int_prompt, int_input);
      mainwindow ()->statusBar ()->removeWidget (leftLabel);
      mainwindow ()->statusBar ()->removeWidget (rightLabel);
      mainwindow ()->statusBar ()->addWidget (prompt, 1);
      prompt->start ();
    }
    else {
      if (prompt) prompt->end ();
      mainwindow ()->statusBar ()->removeWidget (prompt);
      mainwindow ()->statusBar ()->addWidget (leftLabel);
      mainwindow ()->statusBar ()->addPermanentWidget (rightLabel);
      leftLabel->show ();
      rightLabel->show ();
      prompt->deleteLater ();
      prompt= NULL;
    }
  } break;
  case SLOT_FILE: {
    check_type<string> (val, s);
    string file= open_box<string> (val);
    if (DEBUG_QT_WIDGETS) debug_widgets << "\tFile: " << file << LF;
    mainwindow ()->setWindowFilePath (utf8_to_qstring (file));
  } break;
  case SLOT_POSITION: {
    check_type<coord2> (val, s);
    coord2 p           = open_box<coord2> (val);
    QPoint pos         = to_qpoint (p);
    int    screen_count= QGuiApplication::screens ().count ();
    int    screen_w= QApplication::primaryScreen ()->availableSize ().width ();
    if ((screen_count == 1 && screen_w >= pos.x ()) || (screen_count > 1)) {
      // For only 1 screen, only move to pos.x within the screen width
      // For multiple screens, just move it
      mainwindow ()->move (pos);
    }
  } break;
  case SLOT_SIZE: {
    check_type<coord2> (val, s);
    coord2 p= open_box<coord2> (val);
    mainwindow ()->resize (to_qsize (p));
  } break;
  case SLOT_DESTROY: {
    ASSERT (is_nil (val), "type mismatch");
    if (!is_nil (quit)) quit ();
    the_gui->need_update ();
  } break;
  case SLOT_FULL_SCREEN: {
    check_type<bool> (val, s);
    set_full_screen (open_box<bool> (val));
  } break;
  default:
    qt_window_widget_rep::send (s, val);
    return;
  }

  if (DEBUG_QT_WIDGETS)
    debug_widgets << "qt_tm_widget_rep: sent " << slot_name (s)
                  << "\t\tto widget\t" << type_as_string () << LF;
}

blackbox
qt_tm_widget_rep::query (slot s, int type_id) {
  if (DEBUG_QT_WIDGETS)
    debug_widgets << "qt_tm_widget_rep: queried " << slot_name (s)
                  << "\t\tto widget\t" << type_as_string () << LF;

  switch (s) {
  case SLOT_SCROLL_POSITION:
  case SLOT_EXTENTS:
  case SLOT_VISIBLE_PART:
  case SLOT_ZOOM_FACTOR:
    return main_widget->query (s, type_id);

  case SLOT_HEADER_VISIBILITY:
    check_type_id<bool> (type_id, s);
    return close_box<bool> (visibility[0]);

  case SLOT_MAIN_ICONS_VISIBILITY:
    check_type_id<bool> (type_id, s);
    return close_box<bool> (visibility[1]);

  case SLOT_MODE_ICONS_VISIBILITY:
    check_type_id<bool> (type_id, s);
    return close_box<bool> (visibility[2]);

  case SLOT_FOCUS_ICONS_VISIBILITY:
    check_type_id<bool> (type_id, s);
    return close_box<bool> (visibility[3]);

  case SLOT_USER_ICONS_VISIBILITY:
    check_type_id<bool> (type_id, s);
    return close_box<bool> (visibility[4]);

  case SLOT_FOOTER_VISIBILITY:
    check_type_id<bool> (type_id, s);
    return close_box<bool> (visibility[5]);

  case SLOT_SIDE_TOOLS_VISIBILITY:
    check_type_id<bool> (type_id, s);
    return close_box<bool> (visibility[6]);
  case SLOT_BOTTOM_TOOLS_VISIBILITY:
    check_type_id<bool> (type_id, s);
    return close_box<bool> (visibility[7]);
  case SLOT_TAB_PAGES_VISIBILITY:
    check_type_id<bool> (type_id, s);
    return close_box<bool> (visibility[8]);

  case SLOT_INTERACTIVE_INPUT: {
    check_type_id<string> (type_id, s);
    qt_input_text_widget_rep* w=
        static_cast<qt_input_text_widget_rep*> (int_input.rep);
    if (w->ok) return close_box<string> (scm_quote (w->input));
    else return close_box<string> ("#f");
  }

  case SLOT_POSITION: {
    check_type_id<coord2> (type_id, s);
    return close_box<coord2> (from_qpoint (mainwindow ()->pos ()));
  }

  case SLOT_SIZE: {
    check_type_id<coord2> (type_id, s);
    return close_box<coord2> (from_qsize (mainwindow ()->size ()));
  }

  case SLOT_INTERACTIVE_MODE:
    check_type_id<bool> (type_id, s);
    return close_box<bool> (prompt && prompt->isActive ());

  default:
    return qt_window_widget_rep::query (s, type_id);
  }
}

void
qt_tm_widget_rep::install_main_menu () {
  if (main_menu_widget == waiting_main_menu_widget) return;
  main_menu_widget    = waiting_main_menu_widget;
  QList<QAction*>* src= main_menu_widget->get_qactionlist ();
  if (!src) return;
  QMenuBar* dest= mainwindow ()->menuBar ();
  dest->clear ();
  for (int i= 0; i < src->count (); i++) {
    QAction* a= (*src)[i];
    if (a->menu ()) {
      // TRICK: Mac native QMenuBar accepts only menus which are already
      // populated
      //  this will cause a problem for us, since menus are lazy and populated
      //  only after triggering this is the reason we add a dummy action before
      //  inserting the menu
      a->menu ()->addAction ("native menubar trick");
      dest->addAction (a->menu ()->menuAction ());
      QObject::connect (a->menu (), SIGNAL (aboutToShow ()),
                        the_gui->gui_helper, SLOT (aboutToShowMainMenu ()));
      QObject::connect (a->menu (), SIGNAL (aboutToHide ()),
                        the_gui->gui_helper, SLOT (aboutToHideMainMenu ()));
    }
  }
}

void
qt_tm_widget_rep::write (slot s, blackbox index, widget w) {
  if (DEBUG_QT_WIDGETS)
    debug_widgets << "qt_tm_widget_rep::write " << slot_name (s) << LF;

  switch (s) {
    // Widget w is usually a qt_simple_widget_rep, with a QTMWidget as
    // underlying widget. We must discard the current main_widget and
    // display the new. But while switching buffers the widget w is a
    // glue_widget, so we may not just use canvas() everywhere.
  case SLOT_SCROLLABLE: {
    check_type_void (index, s);

    QWidget* q= main_widget->qwid;
    q->hide ();
    QLayout* l= centralwidget ()->layout ();
    l->removeWidget (q);

    q= concrete (w)->as_qwidget (); // force creation of the new QWidget
    l->addWidget (q);
    /* " When you use a layout, you do not need to pass a parent when
     constructing the child widgets. The layout will automatically reparent
     the widgets (using QWidget::setParent()) so that they are children of
     the widget on which the layout is installed " */
    main_widget= concrete (w);
    // canvas() now returns the new QTMWidget (or 0)

    if (scrollarea ()) // Fix size to draw margins around.
      scrollarea ()->surface ()->setSizePolicy (QSizePolicy::Fixed,
                                                QSizePolicy::Fixed);
    send_keyboard_focus (abstract (main_widget));
  } break;

  case SLOT_MAIN_MENU:
    check_type_void (index, s);
    {
      waiting_main_menu_widget= concrete (w);
      if (menu_count <= 0) install_main_menu ();
      else if (!contains (waiting_widgets, this))
        // menu interaction ongoing, postpone new menu installation until done
        waiting_widgets << this;
    }
    break;

  case SLOT_MAIN_ICONS:
    check_type_void (index, s);
    {
      main_icons_widget    = concrete (w);
      QList<QAction*>* list= main_icons_widget->get_qactionlist ();
      if (list) {
        replaceButtons (mainToolBar, list);
        update_visibility ();
      }
    }
    break;

  case SLOT_TAB_PAGES:
    check_type_void (index, s);
    {
      tab_bar_widget       = concrete (w);
      QList<QAction*>* list= tab_bar_widget->get_qactionlist ();
      if (list) {
        tabToolBar->replaceTabPages (list);
        update_visibility ();
      }
    }
    break;

  case SLOT_MODE_ICONS:
    check_type_void (index, s);
    {
      mode_icons_widget    = concrete (w);
      QList<QAction*>* list= mode_icons_widget->get_qactionlist ();
      if (list) {
        replaceButtons (modeToolBar, list);
        update_visibility ();
      }
    }
    break;

  case SLOT_FOCUS_ICONS:
    check_type_void (index, s);
    {
      bool can_update= true;
      // BUG:
      // there is a problem with updateActions  which apparently
      // reset a running input method in Qt5.
      //
      // This is (probably) also relate to
      // bug #47338 [CJK] input disappears immediately
      // see http://lists.gnu.org/archive/html/texmacs-dev/2017-09/msg00000.html

      // HACK: we just disable the focus bar updating while preediting.
      // This seems enough since the other toolbars are not usually updated
      // while performing an input method keyboard sequence
      if (canvas ()) can_update= !canvas ()->isPreediting ();
      if (can_update) {
        focus_icons_widget   = concrete (w);
        QList<QAction*>* list= focus_icons_widget->get_qactionlist ();
        if (list) {
          replaceButtons (focusToolBar, list);
          update_visibility ();
        }
      }
    }
    break;

  case SLOT_USER_ICONS:
    check_type_void (index, s);
    {
      user_icons_widget    = concrete (w);
      QList<QAction*>* list= user_icons_widget->get_qactionlist ();
      if (list) {
        replaceButtons (userToolBar, list);
        update_visibility ();
      }
    }
    break;

  case SLOT_SIDE_TOOLS:
    check_type_void (index, s);
    {
      side_tools_widget   = concrete (w);
      QWidget* new_qwidget= side_tools_widget->as_qwidget ();
      QWidget* old_qwidget= sideTools->widget ();
      if (old_qwidget) old_qwidget->deleteLater ();
      sideTools->setWidget (new_qwidget);
      update_visibility ();
      new_qwidget->show ();
    }
    break;

  case SLOT_BOTTOM_TOOLS:
    check_type_void (index, s);
    {
      bottom_tools_widget = concrete (w);
      QWidget* new_qwidget= bottom_tools_widget->as_qwidget ();
      QWidget* old_qwidget= bottomTools->widget ();
      if (old_qwidget) old_qwidget->deleteLater ();
      bottomTools->setWidget (new_qwidget);
      update_visibility ();
      new_qwidget->show ();
    }
    break;

  case SLOT_INTERACTIVE_PROMPT:
    check_type_void (index, s);
    int_prompt= concrete (w);
    break;

  case SLOT_INTERACTIVE_INPUT:
    check_type_void (index, s);
    int_input= concrete (w);
    break;

  default:
    qt_window_widget_rep::write (s, index, w);
  }
}

void set_standard_style_sheet (QWidget* w);

void
qt_tm_widget_rep::set_full_screen (bool flag) {
  full_screen = flag;
  QWidget* win= mainwindow ()->window ();
  if (win) {
    if (flag) {
      QPalette pal;
      pal.setColor (QPalette::Mid, QColor (0, 0, 0));
      mainwindow ()->setPalette (pal);
#ifdef UNIFIED_TOOLBAR
      if (use_unified_toolbar) {
        // HACK: we disable unified toolbar since otherwise
        //   the application will crash when we return to normal mode
        //  (bug in Qt? present at least with 4.7.1)
        mainwindow ()->setUnifiedTitleAndToolBarOnMac (false);
        mainwindow ()->centralWidget ()->layout ()->setContentsMargins (0, 0, 0,
                                                                        0);
      }
#endif
      //      mainwindow()->window()->setContentsMargins(0,0,0,0);
      // win->showFullScreen();
      win->setWindowState (win->windowState () | Qt::WindowFullScreen);
    }
    else {
      QPalette pal;
      QColor   bgcol= to_qcolor (tm_background);
      pal.setColor (QPalette::Mid, bgcol);
      mainwindow ()->setPalette (pal);
      bool cache   = visibility[0];
      visibility[0]= false;
      update_visibility ();
      //      win->showNormal();
      win->setWindowState (win->windowState () & ~Qt::WindowFullScreen);

      visibility[0]= cache;
      update_visibility ();
#ifdef UNIFIED_TOOLBAR
      if (use_unified_toolbar) {
        mainwindow ()->centralWidget ()->layout ()->setContentsMargins (0, 1, 0,
                                                                        0);
        // HACK: we reenable unified toolbar (see above HACK)
        //   the application will crash when we return to normal mode
        mainwindow ()->setUnifiedTitleAndToolBarOnMac (true);
      }
#endif
    }
  }

  scrollarea ()->setHorizontalScrollBarPolicy (flag ? Qt::ScrollBarAlwaysOff
                                                    : Qt::ScrollBarAsNeeded);
  scrollarea ()->setVerticalScrollBarPolicy (flag ? Qt::ScrollBarAlwaysOff
                                                  : Qt::ScrollBarAsNeeded);
}

/******************************************************************************
 * qt_tm_embedded_widget_rep
 ******************************************************************************/

qt_tm_embedded_widget_rep::qt_tm_embedded_widget_rep (command _quit)
    : qt_widget_rep (embedded_tm_widget), quit (_quit) {
  main_widget= ::glue_widget (true, true, 1, 1);
}

void
qt_tm_embedded_widget_rep::send (slot s, blackbox val) {

  switch (s) {
  case SLOT_INVALIDATE:
  case SLOT_INVALIDATE_ALL:
  case SLOT_EXTENTS:
  case SLOT_SCROLL_POSITION:
  case SLOT_ZOOM_FACTOR:
  case SLOT_MOUSE_GRAB:
    main_widget->send (s, val);
    return;

    /// FIXME: decide what to do with these for embedded widgets
  case SLOT_HEADER_VISIBILITY:
  case SLOT_MAIN_ICONS_VISIBILITY:
  case SLOT_MODE_ICONS_VISIBILITY:
  case SLOT_FOCUS_ICONS_VISIBILITY:
  case SLOT_USER_ICONS_VISIBILITY:
  case SLOT_FOOTER_VISIBILITY:
  case SLOT_SIDE_TOOLS_VISIBILITY:
  case SLOT_BOTTOM_TOOLS_VISIBILITY:
  case SLOT_TAB_PAGES_VISIBILITY:
  case SLOT_LEFT_FOOTER:
  case SLOT_RIGHT_FOOTER:
  case SLOT_SCROLLBARS_VISIBILITY:
  case SLOT_INTERACTIVE_MODE:
  case SLOT_FILE:
    break;

  case SLOT_DESTROY: {
    ASSERT (is_nil (val), "type mismatch");
    if (!is_nil (quit)) quit ();
    the_gui->need_update ();
  } break;

  default:
    qt_widget_rep::send (s, val);
    return;
  }
  if (DEBUG_QT_WIDGETS)
    debug_widgets << "qt_tm_embedded_widget_rep: sent " << slot_name (s)
                  << "\t\tto widget\t" << type_as_string () << LF;
}

blackbox
qt_tm_embedded_widget_rep::query (slot s, int type_id) {
  if (DEBUG_QT_WIDGETS)
    debug_widgets << "qt_tm_embedded_widget_rep::query " << slot_name (s) << LF;

  switch (s) {
  case SLOT_IDENTIFIER: {
    if (qwid) {
      widget_rep* wid= qt_window_widget_rep::widget_from_qwidget (qwid);
      if (wid) return wid->query (s, type_id);
    }
    return close_box<int> (0);
  }

  case SLOT_SCROLL_POSITION:
  case SLOT_EXTENTS:
  case SLOT_VISIBLE_PART:
  case SLOT_ZOOM_FACTOR:
  case SLOT_POSITION:
  case SLOT_SIZE:
    if (!is_nil (main_widget)) return main_widget->query (s, type_id);
    else return qt_widget_rep::query (s, type_id);
    /// FIXME: decide what to do with these for embedded widgets
  case SLOT_HEADER_VISIBILITY:
  case SLOT_MAIN_ICONS_VISIBILITY:
  case SLOT_MODE_ICONS_VISIBILITY:
  case SLOT_FOCUS_ICONS_VISIBILITY:
  case SLOT_USER_ICONS_VISIBILITY:
  case SLOT_FOOTER_VISIBILITY:
  case SLOT_SIDE_TOOLS_VISIBILITY:
  case SLOT_BOTTOM_TOOLS_VISIBILITY:
  case SLOT_TAB_PAGES_VISIBILITY:
    check_type_id<bool> (type_id, s);
    return close_box<bool> (false);

  default:
    return qt_widget_rep::query (s, type_id);
  }
}

widget
qt_tm_embedded_widget_rep::read (slot s, blackbox index) {
  widget ret;

  switch (s) {
  case SLOT_CANVAS:
    check_type_void (index, s);
    ret= main_widget;
    break;
  default:
    return qt_widget_rep::read (s, index);
  }

  if (DEBUG_QT_WIDGETS)
    debug_widgets << "qt_tm_widget_rep::read " << slot_name (s)
                  << "\t\tfor widget\t" << type_as_string () << LF;

  return ret;
}

void
qt_tm_embedded_widget_rep::write (slot s, blackbox index, widget w) {
  if (DEBUG_QT_WIDGETS)
    debug_widgets << "qt_tm_embedded_widget_rep::write " << slot_name (s) << LF;

  switch (s) {
    // Widget w is a qt_simple_widget_rep, with a QTMWidget as underlying
    // widget. We must discard the current QTMWidget and display the new.
    // see qt_tm_widget_rep::write()
  case SLOT_SCROLLABLE: {
    check_type_void (index, s);
    main_widget= w;
  } break;
    /// FIXME: decide what to do with these for embedded widgets
  case SLOT_MAIN_MENU:
  case SLOT_MAIN_ICONS:
  case SLOT_MODE_ICONS:
  case SLOT_FOCUS_ICONS:
  case SLOT_USER_ICONS:
  case SLOT_SIDE_TOOLS:
  case SLOT_BOTTOM_TOOLS:
  case SLOT_TAB_PAGES:
  case SLOT_INTERACTIVE_INPUT:
  case SLOT_INTERACTIVE_PROMPT:
  default:
    qt_widget_rep::write (s, index, w);
  }
}

QWidget*
qt_tm_embedded_widget_rep::as_qwidget () {
  qwid          = new QWidget ();
  QVBoxLayout* l= new QVBoxLayout ();
  l->setContentsMargins (0, 0, 0, 0);
  qwid->setLayout (l);
  l->addWidget (concrete (main_widget)->as_qwidget ());
  return qwid;
}

QLayoutItem*
qt_tm_embedded_widget_rep::as_qlayoutitem () {
  return new QWidgetItem (as_qwidget ());
}
